package com.projectm.project.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.framework.common.AjaxResult;
import com.framework.common.exception.CustomException;
import com.framework.common.utils.ServletUtils;
import com.framework.utils.FileUtils;
import com.projectm.common.Constant;
import com.projectm.common.DateUtil;
import com.projectm.config.MProjectConfig;
import org.springframework.context.ApplicationContext;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.MapUtils;
import org.apache.velocity.runtime.Runtime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;


import javax.crypto.Cipher;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.GZIPOutputStream;

import static com.framework.common.utils.ServletUtils.getResponse;

@RestController
public class CommonController {

    @Autowired
    HttpServletRequest httpServletRequest;
    @Autowired
    HttpServletResponse httpServletResponse;

    @Autowired
    ApplicationContext applicationContext;

    private Map<String, String> keyByPkm5 = new ConcurrentHashMap<>();
    private Map<String, Class> url2Class = new ConcurrentHashMap<>();
    private Map<String, Object> url2Bean = new ConcurrentHashMap<>();
    private Map<String, Class> url2ProxyClass = new ConcurrentHashMap<>();
    private Map<String, Method> url2Method = new ConcurrentHashMap<>();
    private Map<String, Method> url2ProxyMethod = new ConcurrentHashMap<>();

    private String descript(String privateKeyStr, String data) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        byte[] keyArray = privateKeyStr.getBytes();
        byte[] dataArray = data.getBytes();
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(org.apache.commons.codec.binary.Base64.decodeBase64(keyArray));
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);

        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] bytes = Base64.decodeBase64(dataArray);
        try {
            return new String(cipher.doFinal(bytes), "UTF-8");
        }catch(Exception ex) {
            ex.printStackTrace();
            throw ex;
        }
    }

    private String decryptByAes(String encryptPassword, String encryptIv, String data) throws Exception{
        {
            DESKeySpec dks = new DESKeySpec(encryptPassword.getBytes("UTF-8"));

            IvParameterSpec iv = new IvParameterSpec(encryptIv.getBytes());


            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(encryptPassword.getBytes(), "AES"), iv);

            byte[] src = Base64.decodeBase64(data);
            byte[] bytes;
            bytes = cipher.doFinal(src);

            String decryptBytes = new String(bytes, "UTF-8");

            return decryptBytes;
        }
    }
    public String trimRsaKey(String rsaKey) {
        rsaKey = rsaKey
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "")
                .replaceAll("\n", "")
                .replaceAll("\r", "");
        return rsaKey;
    }

    @PostMapping("/common/callEncrypt")
    @ResponseBody
    public AjaxResult callEncrypt(@RequestParam Map<String,Object> mmap)
    {
        if(!StringUtils.hasText(MapUtils.getString(mmap,"enc"))) {
            throw new RuntimeException("缺少参数：enc");
        }
        if(!StringUtils.hasText(MapUtils.getString(mmap,"option"))) {
            throw new RuntimeException("缺少参数：option");
        }
        if(!StringUtils.hasText(MapUtils.getString(mmap,"pkm5"))) {
            throw new RuntimeException("缺少参数：pkm5");
        }
        if(!StringUtils.hasText(MapUtils.getString(mmap,"clientTimestamp"))) {
            throw new RuntimeException("缺少参数：clientTimestamp");
        }

        String pkm5 = httpServletRequest.getHeader("pkm5");
        if(!StringUtils.hasText(pkm5)) {
            throw new RuntimeException("header缺少属性：pkm5");
        }
        String privateKeyStr = keyByPkm5.get(pkm5);
        if(!StringUtils.hasText(privateKeyStr)) {
            String _pkm5 = System.getenv("ENCRYPT_PKM5");
            if(!StringUtils.hasText(_pkm5)) {
                throw new RuntimeException("缺少环境变量：ENCRYPT_PKM5");
            }
            privateKeyStr = System.getenv("ENCRYPT_PRIMARY_KEY");
            if(!StringUtils.hasText(privateKeyStr)) {
                throw new RuntimeException("缺少环境变量：ENCRYPT_PRIMARY_KEY");
            }
            privateKeyStr = trimRsaKey(privateKeyStr);
            keyByPkm5.put(pkm5, privateKeyStr);
        }

        String encStr = MapUtils.getString(mmap,"enc");

        try {
            encStr = descript(privateKeyStr, encStr);
        }catch (Exception ex) {
            throw new RuntimeException("无法解密option:"+ex.getMessage());
        }
        String encryptPassword = encStr.substring(0, 16);
        String encryptIv = encStr.substring(16);


        String queryStr = MapUtils.getString(mmap,"query");
        try {
            queryStr = decryptByAes(encryptPassword, encryptIv, queryStr);
        }catch (Exception ex) {
            throw new RuntimeException("无法解密query："+ex.getMessage());
        }

        JSONObject query;
        try {
            query = JSONObject.parseObject(queryStr);
        }catch (Exception ex) {
            throw new RuntimeException("option格式非法："+ex.getMessage());
        }

        String optionStr = MapUtils.getString(mmap,"option");
        try {
            optionStr = decryptByAes(encryptPassword, encryptIv, optionStr);
        }catch (Exception ex) {
            throw new RuntimeException("无法解密option："+ex.getMessage());
        }

        JSONObject option;
        try {
            option = JSONObject.parseObject(optionStr);
        }catch (Exception ex) {
            throw new RuntimeException("option格式非法："+ex.getMessage());
        }
        String url = option.getString("url");
        if(!StringUtils.hasText(url)) {
            throw new RuntimeException("option缺少属性：url");
        }

        if(!url.startsWith("/")) {
            url = "/"+url;
        }

        synchronized (url2Class) {
            if(CollectionUtils.isEmpty(url2Class)) {
                Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(RestController.class);
                for (Object bean : beansWithAnnotation.values()) {
                    Class proxyBeanClz = bean.getClass();
                    Class<?> beanClass = bean.getClass();
                    Map<String, Method> proxyBeanMethodMap = new HashMap<>();
                    {
                        Class c = proxyBeanClz;
                        while(c != null) {
                            Method[] ms = c.getDeclaredMethods();
                            for(Method m:ms) {
                                proxyBeanMethodMap.put(m.getName(), m);
                            }
                            c = c.getSuperclass();
                        }
                    }
                    if (!beanClass.isAnnotationPresent(RequestMapping.class)) {
                        String name = beanClass.getName();
                        if(name.indexOf("$$") > 0) {
                            name = name.split("\\$\\$")[0];
                            try {
                                beanClass = Class.forName(name);
                            }catch (Exception ex) {

                            }
                        }
                    }
                    if (beanClass.isAnnotationPresent(RequestMapping.class)
                            || beanClass.isAnnotationPresent(RestController.class)
                    ) {
                        String[] basePaths;
                        if(beanClass.isAnnotationPresent(RequestMapping.class)) {
                            RequestMapping requestMapping = beanClass.getAnnotation(RequestMapping.class);
                            basePaths = requestMapping.value();
                        } else {
                            basePaths = new String[]{""};
                        }

                        for (String basePath : basePaths) {
                            for (Method method : beanClass.getDeclaredMethods()) {
                                if(method.isAnnotationPresent(PostMapping.class)) {
                                    PostMapping methodRequestMapping = method.getAnnotation(PostMapping.class);
                                    String[] methodPaths = methodRequestMapping.value();
                                    for (String methodPath : methodPaths) {
                                        url2Bean.put(basePath+methodPath, bean);
                                        url2ProxyClass.put(basePath+methodPath, proxyBeanClz);
                                        url2Class.put(basePath+methodPath,  beanClass);
                                        url2Method.put(basePath+methodPath, method);
                                        if(proxyBeanMethodMap.containsKey(method.getName())) {
                                            url2ProxyMethod.put(basePath+methodPath, proxyBeanMethodMap.get(method.getName()));
                                        }
                                    }
                                    continue;
                                }
                            }
                        }
                    }
                }
            }
        }
        Class clz = url2Class.get(url);
        Method method = url2Method.get(url);
        Class proxyClz = url2ProxyClass.get(url);
        Method proxyMethod = url2ProxyMethod.get(url);
        Object bean = url2Bean.get(url);
        if(bean != null && proxyClz != null && proxyMethod != null) {
            try {
                proxyMethod.setAccessible(true);
                httpServletRequest.removeAttribute("fixResultMethod");
                if (option.containsKey("sessionid")) {
                    httpServletRequest.setAttribute("sessionid", option.getString("sessionid"));
                }
                httpServletRequest.setAttribute("discardResponse", true);
                Object ret;
                if (proxyMethod.getParameters().length == 1) {
                    Object param = JSON.parseObject(queryStr, proxyMethod.getParameters()[0].getType());
                    ret = proxyMethod.invoke(bean, param);
                } else {
                    ret = proxyMethod.invoke(bean);
                }

                if (ret == null) {
                    throw new RuntimeException("禁止返回空值：" + url);
                }
            } catch(Exception ex) {
                throw new RuntimeException(ex);
            }
        }


        String _encryptPassword = option.getString("encryptPassword");
        if(!StringUtils.hasText(_encryptPassword)) {
            throw new RuntimeException("option缺少属性：encryptPassword");
        }
        String _encryptIv = option.getString("encryptIv");
        if(!StringUtils.hasText(_encryptIv)) {
            throw new RuntimeException("option缺少属性：encryptIv");
        }
        String clientTimestampStr = option.getString("clientTimestamp");
        if(!StringUtils.hasText(clientTimestampStr)) {
            throw new RuntimeException("option缺少属性：clientTimestampStr");
        }

        SimpleDateFormat datetimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//        Date clientTimestamp = datetimeFormat.parse(option.getString("clientTimestamp"));

        String sessionid = option.getString("sessionid");
        throw new RuntimeException("not implement:"+url);

//        return AjaxResult.success();
    }

    @GetMapping("/common/checkJenkins")
    public String checkJenkins(@RequestParam Map<String,Object> mmap)
    {
        String mobile = (String)mmap.get("mobile");

        if(!org.springframework.util.StringUtils.hasText(mobile)) {
            throw new RuntimeException("参数缺少电话号码");
        }

        if(!mobile.matches("[0-9]+")) {
            throw new RuntimeException("电话号码非法");
        }

        String userHome = System.getProperty("user.home");
        System.out.println("用户主目录: " + userHome);

        if(!StringUtils.hasText(userHome)) {
            throw new RuntimeException("无法确认服务主目录");
        }

        String path = userHome+"/.jenkins";
        if(!new File(path).isDirectory()) {
            throw new RuntimeException("服务主目录缺少目录: .jenkins");
        }

        File f = new File(path+"/"+mobile+".teamwork");
        try {
            FileInputStream fo = new FileInputStream(f);
            byte[] bs = new byte[102400];
            fo.read(bs);
            fo.close();;
            JSONObject jsonObject = JSON.parseObject(new String(bs, StandardCharsets.UTF_8));
            String tspStr = jsonObject.getString("tsp");
            Date tsp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(tspStr);
            long diff = Math.abs(tsp.getTime() - (new Date().getTime()));
            if (diff > 10 * 1000) {
                throw new RuntimeException("登录时间过长，请重试");
            }
            System.out.println("checkJenkins:"+mobile+","+tspStr+","+diff);
            return "name:"+jsonObject.getString("memberName")+":\n";
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 通用下载请求
     *
     * @param filePathName 文件路径和文件名称
     * @param delete 是否删除
     */
    @GetMapping("/common/download")
    public void fileDownload(@RequestParam Map<String,Object> mmap)
    {
        String filePathName =  MapUtils.getString(mmap,"filePathName");
        String realFileName =  MapUtils.getString(mmap,"realFileName");
        boolean delete =  MapUtils.getBoolean(mmap,"delete",false);
        try
        {
            /*if (!FileUtils.isValidFilename(filePathName))
            {
                throw new CustomException(StringUtils.format("文件名称({})非法，不允许下载。 ", filePathName));
            }*/
            String filePath = MProjectConfig.getProfile() + filePathName;

            getResponse().setCharacterEncoding("utf-8");
            getResponse().setContentType("multipart/form-data");
            getResponse().setHeader("Content-Disposition",
                    "attachment;fileName=" + FileUtils.setFileDownloadHeader(ServletUtils.getRequest(), realFileName));
            FileUtils.writeBytes(filePath, getResponse().getOutputStream());
            if (delete)
            {
                FileUtils.deleteFile(filePath);
            }
        }
        catch (Exception e)
        {

        }
    }

    @GetMapping("/common/image")
    public void image(@RequestParam Map<String,Object> mmap)
    {
        String filePathName =  MapUtils.getString(mmap,"filePathName");
        String realFileName =  MapUtils.getString(mmap,"realFileName");
        try
        {

            String filePath = MProjectConfig.getProfile() + filePathName;

            FileUtils.writeBytes(filePath, getResponse().getOutputStream());

        }
        catch (Exception e)
        {e.printStackTrace();
            throw new CustomException(e.getMessage());
        }
    }
}
